
#ifndef AVDL_TIMMOD_H
#define AVDL_TIMMOD_H

#ifdef __cplusplus
extern "C" {
#endif

#include"timmod_test.h"

#ifdef _WIN32_USER_
#include <tchar.h>
#include <windows.h>

#define inline __inline
	typedef signed char  int8_t;
	typedef signed short int16_t;
	typedef signed int   int32_t;
	typedef unsigned char  uint8_t;
	typedef unsigned short uint16_t;
	typedef unsigned int   uint32_t;

	typedef signed __int64   int64_t;
	typedef unsigned __int64 uint64_t;
	typedef uint64_t suseconds_t;
/*
	typedef union _LARGE_INTEGER
		{
			   struct
			   {
				  uint32_t LowPart ;// 4
				  uint32_t  HighPart;// 4
			   };
			   uint64_t QuadPart ;// 8
	           
			}LARGE_INTEGER ;
*/

#else
#include <stdint.h>
#include <sys/time.h>

#endif

#include <stdio.h>


#ifdef _USER_TEST_
#define printk printf
#define OS_INFO printf
#define os_lock(...)
#define os_unlock(...)
#endif

#ifdef _WIN32_USER_
#define get_pc_time(a)\
	do{\
	LARGE_INTEGER lv,lv1;\
	QueryPerformanceFrequency(&lv1);\
	QueryPerformanceCounter( &lv );\
	*a=(lv.QuadPart*90000)/lv1.QuadPart;\
	}while(0);

#else
#define get_pc_time(a)\
	do{\
		struct timeval tv1;\
		gettimeofday(&tv1,NULL);\
		*a=(uint32_t)tv1.tv_sec*1000*1000+tv1.tv_usec;\
	}while(0);
#endif

   


typedef uint32_t avdl_timmod_channel_handle_t;
typedef uint32_t avdl_timmod_handle_t;


//2 instance maximumly to support PIP
#define MAX_INSTANCE    2

//4 channels for each instance (audio, video, subtitle, teletext, ..)
#define MAX_CHANNEL     8

//Number of points tracked for each channel
#define NUMBER_OF_HISTORY_DATA     1024 

//The MAX total number of points for calculating average delta for each channel
#define POINTS_OF_AVERAGE_DELTA    256 

//Theory buffer level, currrently using 0.5s
//TODO: !!DYNAMICALLY PROFILING THIS BY MONITORING BUFFER LEVEL OF SOURCE AND RENDER
//TODO: increase this if you want to increase pts'(out)
#define TIMMOD_THEORY_BUFFERING   45000 

//Threshold used for the low pass filter of the secondary delta
#define TIMMOD_SECONDARY_LP_THRESHOLD   90000

//Threshold used for the low pass filter of pts delta, to make sure all output 
//of PTS' is between an upper and lower of the theory line.
//TODO: profile this.
#define TIMMOD_LP_THRESHOLD   90000

//To calculated the final average delta, each channel's average delta will be calculated,
//The difference between each channel's average delta should not exceed a threshold, 
//otherwise, there must be some error happens
//Generally, the gap between video and audio PTS should not exceed 500ms
#define DIFFERENCE_BETWEEN_AVERAGE_DELTA_THRESHOLD       900000  //10s

//Number of points needed to enter the STABLE state,TODO: profiling this
//TODO: this should be big enough to make sure all channels safely transited 
//to next stable stage (after a discontinuity) before the timmod reach new stable status.
#define TIMMOD_STABLIZE_POINTS  5 

//The average delta should not be changed too frequently 
#define AVERAGE_DELTA_CHANGE_FREQUENCY          9000000     //100s

//Avoid average delta floating.
#define AVERAGE_DELTA_FLOATING_THRESHOLD        22500

typedef uint64_t ismd_time_t;
typedef uint32_t avdl_vtc_handle_t;
typedef uint32_t avdl_tsi_handle_t;
typedef uint32_t ismd_clock_handle_t;
typedef void *os_lock_t;
//TODO: further consideration of the data burst case and mismatched input case
typedef enum {
   TIMMOD_STATE_START = 0,
   TIMMOD_STATE_STABLE = 1,
   TIMMOD_STATE_DISCONTINUITY = 2,
} timmod_state_t;

typedef enum {
   ISMD_SUCCESS = 0,
}ismd_result_t;

typedef enum {
   NORMAL_TEST = 0,
   DISCONTINUITY_TEST ,
   ODD_POINT_TEST ,
   LOOPBACK_TEST ,
}test_case_t;

typedef struct {
   ismd_time_t pts_in;
   ismd_time_t pts_out;
   ismd_time_t arrival_time;
   ismd_time_t using_time;
   ismd_time_t pts_theory_by_arrival_time;
   ismd_time_t pts_theory_by_using_time;
   int64_t delta_arrival;
   int64_t delta_using;
   int64_t secondary_delta_arrival;
   int64_t secondary_delta_using;
} point_t;

typedef struct {
   //TODO: Can move to timmod_t if only used in open...
   os_lock_t lock;
   uint32_t channel_index;
   int used;
   avdl_vtc_handle_t render_handle;
   //When system startup, all channel's state is START
   //When first point arrived, the channel and TimMod's state will change to STABLE
   //If one secondary_delta (delta') not pass the LP, channel's state will change from STABLE to DISCONTINUITY
   //If a serials of continuous points pass the LP, channel's state will change from DISCONTINUITY to STATBLE
   timmod_state_t state;
   uint32_t write;
   uint32_t num_of_continuous_points;
   //number of valid points
   uint32_t num_of_points;
   //This channel's contribution to average_delta
   ismd_time_t last_average_delta;
   ismd_time_t prev_deltau;
   ismd_time_t prev_deltaa;
   //First point of this channel. 
   //This should be differentiated because on first point, you can not calc the secondary delta.
   int first_point;
   point_t p[NUMBER_OF_HISTORY_DATA];
} channel_t;


typedef struct {
   //Multi-channel may access this object simultaneously
   int used;
   os_lock_t lock;
   ismd_time_t base_time;
   ismd_clock_handle_t clock_handle;
   avdl_tsi_handle_t *source_handle;
   //average_delta last change time in clock time (not in stream time).
   ismd_time_t average_delta_last_change_time;
   int play_rate;
   //An average delta that will be used for PTS transformation.
   //This should not change frequently
   //Points to change this delta:
   //1. System startup guessing stage, during which average delta may changed relatively frequent.
   //2. When state change from DISCONTINUITY -> STATBLE
   //3. When system error accumulated to a certain amount (in STABLE state)
   int64_t average_delta;
   //TimMod's state: Channel state is changed between STABLE and DISCONTINUITY after startup.
   //TimMod's state is STABLE when ALL channels is STABLE; 
   //TimMod's state is DISCONTINUITY if there is one channel in DISCONTINUITY; 
   //TimMod's state is START if there is one channel in START 
   timmod_state_t state;
   channel_t channel[MAX_CHANNEL];
} timmod_t;

/** AVDL Return codes \enum avdl_result_t */
typedef enum {
   AVDL_SUCCESS = 0,                /**< 0 */
   AVDL_INVALID_HANDLE,             /**< 1 */
   AVDL_INVALID_PARAM,              /**< 2 */
   AVDL_FEATURE_NOT_SUPPORTED,      /**< 3 */
   AVDL_FEATURE_NOT_IMPLEMENTED,    /**< 4 */
   AVDL_FATAL_ERROR,                /**< 5 */
   AVDL_FAILURE,                    /**< 6 */
   AVDL_QUEUE_FULL,                 /**< 7 */
   AVDL_QUEUE_EMPTY,                /**< 8 */
   AVDL_WORKLOAD_INUSE,           /**<9 */
   AVDL_NO_AVAIL_WORKLOAD, /**<10 */
   AVDL_FAILED_MEMORY_ALLOCATION, /**<11 */
   AVDL_NO_RESOURCES, /**<12 */
   AVDL_INTR_NOT_PENDING, /**<13 */
   AVDL_ERR_HEAP_ALLOC, /**<14 */
   AVDL_NULL_POINTER, /**<15 */
   AVDL_FRAME_SIZE_NOT_SET, /**<16 */
   AVDL_INVALID_CONFIGURATION, /**<17 */
   AVDL_INVALID_REQUEST, /**<18 */
   AVDL_DEVICE_BUSY, /**<19 */
   AVDL_DEVICE_NOT_BUSY, /**<20 */
   AVDL_OSAL_FAILURE,/**<21*/
   AVDL_PAL_FAILURE,/**<22*/
   AVDL_RESOURCES_INUSE,/**<23*/
   AVDL_NOT_ENOUGH_PHYS_REGION,/**<24*/
   AVDL_PHYS_REGION_NOT_ALIGNED, /**<25*/
   AVDL_PHYS_REGION_NOT_DEFINED, /**<26*/
   AVDL_FW_BOOT_ERROR, /**<27*/
   AVDL_INSUFFICIENT_MEMORY_FOR_FW, /**<28*/
   AVDL_FW_IPC_TIMEOUT, /**<29*/
   AVDL_FW_IPC_FAILURE, /**<30*/
   AVDL_NOT_CONNECTED, /**<31*/
   AVDL_IOCTL_ERROR, /**<32*/
   AVDL_DRIVER_OPEN_FAILURE, /**<33*/
   AVDL_SANITY_CHECK_FAILURE, /**<34*/
   AVDL_NO_DESCRIPTORS_AVAILABLE, /**<35*/
   AVDL_CANNOT_DEALLOCATE_FREE_NODE, /**<36*/
   AVDL_DEVICE_NOT_READY, /**<37*/
   AVDL_TIMEOUT, /**<38*/
} avdl_result_t;

/**      
Destroy TimMod Module. This can only be called on rmmod 
*/
void timmod_deinit();

/**      
Init TimMod Module. This can only be called on system initialization 
*/
void timmod_init();

/**      
Allocates a TimMod handle.  Will set up a TimMod instance.

@param[out] handle : the returned handle of TimMod

@retval AVDL_SUCCESS : timmod instance allocated successfully  
@retval AVDL_INVALID_PARAM : bad parameters       
@retval AVDL_NO_RESOURCE : run out of resources 
*/
avdl_result_t avdl_timmod_open(avdl_timmod_handle_t *handle);

/**      
Free a TimMod handle.  Will close a TimMod instance.

@param[in] handle : handle of TimMod

@retval AVDL_SUCCESS : timmod instance closed successfully  
@retval AVDL_INVALID_HANDLE : bad timmod handle
*/
avdl_result_t avdl_timmod_close(avdl_timmod_handle_t handle);

/**      
Allocates TimMod channel handle.  Will set up an TimMod channel.

@param[in] render_handle : audio/video/subtile device handle. This handle is used to get render buffer level.  
@param[out] timmod_channel_handle : the returned channel handle of TimMod

@retval AVDL_SUCCESS : timmod channel allocated successfully  
@retval AVDL_INVALID_PARAM : bad parameters       
@retval AVDL_NO_RESOURCE : run out of resources 
*/      
avdl_result_t avdl_timmod_channel_open(avdl_timmod_handle_t timmod_handle, int render_handle, avdl_timmod_channel_handle_t *timmod_channel_handle);

/**
This function closes the specified channel in the timing model module. All resources allocated
for the channel it owns are automatically freed.

@param[in] timmod_handle : timing model module handle.  
@param[in] timmod_channel_handle : channel handle.  The handle should
not be used after this function has been called.

@retval AVDL_SUCCESS : The channel is closed successfully.
@retval AVDL_INVALID_HANDLE : handle is not a valid timmod channel handle.
*/
avdl_result_t avdl_timmod_channel_close(avdl_timmod_handle_t timmod_handle, avdl_timmod_channel_handle_t timmod_channel_handle);

/**      
Set TimMod base time. This should always be called before play back start or after channel change

@param[in] timmod_handle : timing model module handle.  
@param[in] base_time: stream base time. 

@retval AVDL_SUCCESS : set base time succussfully 
*/
avdl_result_t avdl_timmod_set_base_time(avdl_timmod_handle_t timmod_handle, ismd_time_t base_time);

/**      
Set TimMod clock handle. This should always be called before play back start or after channel change
TimMod use this handle to read the current clock counter.

@param[in] timmod_handle : timing model module handle.  
@param[in] handle: a ismd clock handle 

@retval AVDL_SUCCESS : set clock handle succussfully 
*/
avdl_result_t avdl_timmod_set_clock(avdl_timmod_handle_t timmod_handle, ismd_clock_handle_t handle);

/**      
Set TimMod tsi handle. This should always be called before play back start or after channel change
TimMod use this handle to read the tsi buffer level 

@param[in] timmod_handle : timing model module handle.  
@param[in] handle: a tsi handle 

@retval AVDL_SUCCESS : set clock handle succussfully 
*/
avdl_result_t avdl_timmod_set_source(avdl_timmod_handle_t timmod_handle, avdl_tsi_handle_t *handle);


/**      
Set TimMod play rate. This should always be called before play back start or after channel change
TimMod use this to calculate the PTS theory line. The default playrate is 1. 

@param[in] timmod_handle : timing model module handle.  
@param[in] play_rate: play rate 

@retval AVDL_SUCCESS : set play rate succussfully 
*/
avdl_result_t avdl_timmod_set_play_rate(avdl_timmod_handle_t timmod_handle, int play_rate);

/**
Main function to do PTS transformation.

@param[in] timmod_handle : timing model module handle.  
@param[in] timmod_channel_handle : channel handle.  
@param[in] pts: the original PTS.
@param[in] arrival_time: the time (in stream time) that the PTS arrived demux.
@param[in] rending_time: the time (in stream time) that the PTS rendered by render. 
@param[out] pts_out: used to return the transformed PTS. If *pts_out == 0, then the 
                     current PTS was invalidated by TimMod.

@retval AVDL_SUCCESS : PTS transform successfully 
@retval AVDL_INVALID_HANDLE : handle is not a valid timmod channel handle or pts_out == NULL
*/
avdl_result_t avdl_timmod_pts_transform(avdl_timmod_handle_t timmod_handle, 
                                        avdl_timmod_channel_handle_t timmod_channel_handle, 
                                        ismd_time_t pts_in, ismd_time_t arrival_time, 
                                        ismd_time_t using_time, ismd_time_t *pts_out);

#ifdef __cplusplus
}
#endif

#endif
